Mise à jour le 29/01/2022
Migrer une application de Symfony 3.4 à 5.4

Migrer une application de Symfony 3.4 à 5.4


Si votre application tourne sous Symfony 3.4 et que vous avez un peu de temps devant vous, je vous conseille de passer directement le code afin qu'il soit directement compatible avec Symfony 5.4 (actuellement la version stable de Symfony).

Voici quelques pièges à éviter pour que la migration soit la plus efficace possible :

1. Le fichier bundles.php

Le fichier bundles.php est le fichier où sont chargés tous les vendors spécifiques à Symfony (que l'on nomme toujours Bundle).

L'ordre des bundles définie la priorité de chargement.
C'est à dire que si l'un des vendors a une dépendance avec un autre, il est important de vérifier que le vendor nécessaire est chargé avant.

Ainsi, je vous conseille de commencer par toujours charger les composants Symfony avant les composants tiers.

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],

    My\Extra\MyExtraBundle\MyExtraBundle::class => ['all' => true],
];

Ici, le bundle MyExtraBundle est chargé après ses éventuelles dépendances.

Pour connaitre la liste des dépendances d'un Bundle, il suffit de regarder le composer.json du Bundle tier.

2. Les fichiers de config .yaml

Contrairement à Symfony 3.4 où la configuration se trouve dans le dossier app/config, en SF5.4, tout se trouve dans le dossier config.

Si vous avez besoin que les deux versions de l'application co-existent pendant un temps (ie. conserver les fichiers de configuration dans le dossier app/config), il est tout à fait possible d'importer les .yml de la version 3.4 dans les .yaml de la version 5.4.

Egalement, il faut faire attention à l'ordre de priorité des fichiers de configuration.

Par exemple, le fichier package/dev/special_config.yaml sera moins prioritaire que le fichier package/special_config.yaml et sera ignoré même si vous chargez le projet en mode dev.
Il faut alors :
- soit supprimer le fichier package/special_config.yaml et le recréer pour chaque environnement package/{environnement}/special_config.yaml
- soit supprimer le fichier package/dev/special_config.yaml et créer le fichier package/special_config_dev.yaml

3. Remplacement d'assetic par Webpack Encore

Pour installer Webpack Encore, il suffit de lire la doc officielle (https://symfony.com/doc/current/frontend/encore/installation.html).

3.1 Piège : les fonctions de vos .js ne seront plus globales par défaut

Le gros piège avec cette migration est que le code de vos fichiers .js ne sera plus global. C'est à dire que si vous utilisez certaines fonctions directement dans votre html (soit avec la balise <script> soit dans les attributs HTML), elles ne pourront pas fonctionner si elles ne sont pas déclarées globlales.

Pour déclarer une fonction globale, il y a au moins trois façons de faire :

- soit vous utilisez le mot clef "global." qui est convertie par Webpack en fonction du contexte pour que la fonction soit disponible ;
- soit vous utilisez le mot clef "window." qui ne nécessite pas d'être convertie par Webpack et qui fonctionnera également dans la version Assetic du projet ;
- soit vous déclarez 'inline' le script directement dans le fichier de template twig.

3.1.1 Version compatible application sous Assetic ET application sous Webpack

Avant :
function toto(arg) {
    // ...
}

Après : 
window.toto = function(arg) {
    // ...
}


3.1.2 Version compatible uniquement via Webpack

Avant :
function toto(arg) {
    // ...
}

Après :
global.toto = function(arg) {
    // ...
}


3.2 Piège : la transpilation sass interdit les extends dans les balises @media

Note : ce qui suit est à prendre avec des pincettes.

Alors qu'avec Assetic (et son transpileur sass), il est possible de faire des extends à l'intérieur de tag @media. Via webpack, cela n'est plus possible, il faut alors :
- soit sortir le extends du @media et faire un extends d'un pointeur déjà lié à un @media

@extends .justify-content-sm-center;


- soit mettre l'équivalence css directement dans le tag @media (eg: "justify-content:center;")

@media

  justify-content:center;
}


3.3 Piège : jquery ou $ ne fonctionne plus

Egalement, en chargeant jquery via Webpack, l'alias $ ne sera plus disponible pour les autres scripts.
Il faut indiquer à Webpack de rendre disponible jQuery de façon globale.

Fichier app.js
import "jquery";
window.$ = window.JQuery = jQuery;


Note : il est possible aussi d'utiliser le mot clef "global" à la place de "window".

3.4 Piège : priorité des librairies

Egalement, l'ordre dans lequel sont déclarés les fichiers .js dans le app.js est important, il faut commencer par les librairies fondamentales.
(ex: jquery > jqueryui > bootstrap > bootstrap-plugin1... > librairie-js-quelconque)

4. Chargement des templates

Sous SF5.4, les fichiers de templates twig doivent normalement se trouver dans les dossiers "templates".
Il est possible de conserver les dossiers "Resources" le temps de la bascule de la nouvelle version afin de simplifier la maintenance entre les deux versions de l'application.

Pour déclarer le dossier Resources/views comme un dossier de templates, il suffit de modifier la configuration de twig de cette façon :

# config/packages/twig.yaml
twig:
paths:
    'OldBundle/Resources/views': 'Old'


Les fichiers de templates seront alors toujours accessibles via "@Old".

4.1 Piège : l'utilisation du double points (ou colon) ':' pour charger les templates

A priori (je n'ai pas trouvé d'historique sur le sujet), il n'est plus possible d'utiliser le double-points pour indiquer le chemin d'un template, il est possible d'utiliser le slash, qui marchera quelque soit la version de Symfony :

'@Old/Domain:index.html.twig' -> ne fonctionne plus sous Symfony 5.4
'@Old/Domain/index.html.twig' -> fonctionnera sous Symfony 3.4 et Symfony 5.4


Cela vaut pour tous les chargements :

Dans les fichiers *.twig : 
{% use ''
{% extends ''
{% embed ''
{% form_theme form ''

Dans les fichiers *.php :
->render('')


5. L'usage de block twig non déclarés

Si le block n'existe pas, cela déclenchera une erreur.

{# Erreur lors du render #}
{{ block('block_inexistant') }} 


6. Le routing des Controllers

6.1 Dans les fichiers routing.yml

Il vaut mieux oublier la syntaxe avec les deux-points dans les fichiers de type routing.yml :

Avant : 
nom_route: 
    path: /
    defaults : {_controller : "AppBundle:Domain:index"}

Après :
nom_route:
    path: /
    defaults : {_controller : AppBundle\Controller\DomainController::indexAction}


6.2 Dans les fichiers de templates .twig

Même chose dans les templates, il vaut mieux éviter d'utiliser les deux points comme séparateur.

Avant :
{{ render(controller('AppBundle:Domain:index')) }}

Après :
{{ render(controller('AppBundle\\Controller\\DomainController:indexAction')) }}


7. L'utilisation du ->get dans les Controller

En Symfony 5.4, le ->get('nom_service') devient obsolète.
Il est toujours utilisable pour les services classiques de Symfony (router / twig / session) mais ne l'est plus pour les services personnalisés de votre projet.

Il devient alors nécessaire de déclarer les services dans les constructeurs de vos controlleurs, de les charger via l'autowiring (ou sans en déclarant manuellement les noms des services dans les fichiers services.yaml/xml).

Avant : 
public function indexAction()
{
    $this->get('mon_service')->do();
    // ...
}

Après (ici le code en PHP 7.4): 
private MonService $monService;

public function __construct(MonService $monService)
{
    $this->monService = $monService;
}

public function indexAction(): Response
{
    $this->monService->do();
    // ...
}


Note : le suffixe "Action" est conservé toujours dans l'optique où l'ancienne version du code sous SF3.4 est toujours en fonctionnement.

8. L'utilisation du getParameter

De la même façon, il n'est plus possible de récupérer un paramètre de configuration via la méthode getParameter du Controller.

Il y a alors au moins deux façons de faire :
- soit injecter dans le construct le service ParameterBag :

private ParameterBagInterface $monService;

public function __construct(ParameterBagInterface $parameterBag)
{
    $this->parameterBag = $parameterBag;
}

public function indexAction(): Response
{
    $this->parameterBag->get('nom_parametre'); // Valeur du paramètre
    // ...
}


💡️On peut même créer une méthode protected function getParameter(string $name) qui retournerait $this->parameterBag->get('nom_parametre')


- soit injecter directement le paramètre dans le construct et utiliser l'autoconfigure dans le services.yaml (ou le déclarer manuellement également).

private string $parameter;

public function __construct(string $parameter)
{
    $this->parameter = $parameter;
}

public function indexAction(): Response
{
    $this->parameter; // Valeur du paramètre
    // ...
}



9. Piège : utiliser des choses supprimées

9.0.1 $form->isValid()

Il n'est plus possible d'utiliser isValid() directement sans vérifier au préalable que le formulaire est soumis.
L'utilisation de la méthode isValid doit toujours être précédée par la méthode isSubmitted.

Il faut donc faire ceci :

Avant : 
$form->isValid();

Après :
$form->isSubmitted() && $form->isValid();


9.0.2 choices_as_values

Le paramètre choices_as_values dans les FormType n'existe plus. Il faut le supprimer en veillant à tester que les formulaires fonctionnent toujours.

9.0.3 new Email(['strict' => true])

Le validator Email (Symfony/Component/Validator/Constraints/Email) a changé, il ne faut plus utiliser le paramètre 'strict', il faut le remplacer par le paramètre 'mode'.

Avant :
new Email(['strict' => true]);

Après :
new Email(['mode' => Email::VALIDATION_MODE_STRICT]);


9.0.4 transchoice

La méthode 'transchoice' n'existe plus, il faut toujours passer par la méthode 'trans' et utiliser le paramètre %count% pour indiquer une quantité.

📖️️Documentation officielle : https://symfony.com/doc/current/translation.html#message-format


9.0.5 $eventDispatcher->dispatch('nom.event', $event)

La méthode dispatch ne prend plus qu'un seul argument, à savoir l'instance de la classe Event :

Avant :
$eventDispatcher->dispatch('nom.event', $event)

Après :
$eventDispatcher->dispatch($event)


9.0.6 Balises twig javascripts

Evidemment, vu qu'Assetic est aux oubliettes, les tags twig {% javascripts '...' %}{% endjavascripts %} et {% stylesheets '...' %}{% endstylesheets %} ne marcheront plus.
Ils sont remplacés par leurs homologues {{ encore_entry_script_tags('...') }} et {{ encore_entry_link_tags('...') }}

9.0.7 Injecter un paramètre sans l'autoconfigure et sans le Configuration.php

Il est possible d'injecter un paramètre en argument d'un service en le déclarant dans le services.yaml :

Avant (en passant par la méthode getConfigTreeBuilder de la classe DependencyInjection/Configuration.php du AppBundle):
nom_service: 
    class: App\MyClass
    arguments:
        - "%key.index%"

Après (en injectant directement le paramètre dans le service):
nom_service:
    class: App\MyClass
    arguments: 
        - "@parameter('key')['index']"